package com.luxsoft.sw3.cfdi;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.xml.namespace.QName;

import mx.gob.sat.cfd.x3.ComprobanteDocument;
import mx.gob.sat.cfd.x3.ComprobanteDocument.Comprobante;
import mx.gob.sat.cfd.x3.ComprobanteDocument.Comprobante.Conceptos;
import mx.gob.sat.cfd.x3.ComprobanteDocument.Comprobante.Conceptos.Concepto;
import mx.gob.sat.cfd.x3.ComprobanteDocument.Comprobante.Emisor;
import mx.gob.sat.cfd.x3.ComprobanteDocument.Comprobante.Emisor.RegimenFiscal;
import mx.gob.sat.cfd.x3.ComprobanteDocument.Comprobante.Impuestos;
import mx.gob.sat.cfd.x3.ComprobanteDocument.Comprobante.Impuestos.Traslados;
import mx.gob.sat.cfd.x3.ComprobanteDocument.Comprobante.Impuestos.Traslados.Traslado;
import mx.gob.sat.cfd.x3.ComprobanteDocument.Comprobante.Receptor;
import mx.gob.sat.cfd.x3.ComprobanteDocument.Comprobante.TipoDeComprobante;
import mx.gob.sat.cfd.x3.TUbicacion;
import mx.gob.sat.cfd.x3.TUbicacionFiscal;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlDateTime;
import org.apache.xmlbeans.XmlOptions;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.luxsoft.siipap.cxc.model.NotaDeCredito;
import com.luxsoft.siipap.cxc.model.NotaDeCreditoBonificacion;
import com.luxsoft.siipap.cxc.model.NotaDeCreditoDet;
import com.luxsoft.siipap.model.CantidadMonetaria;
import com.luxsoft.siipap.model.Direccion;
import com.luxsoft.siipap.model.Empresa;
import com.luxsoft.siipap.model.core.Cliente;
import com.luxsoft.siipap.service.KernellSecurity;
import com.luxsoft.siipap.util.MonedasUtils;
import com.luxsoft.siipap.ventas.model.Venta;
import com.luxsoft.siipap.ventas.model.VentaDet;
import com.luxsoft.sw3.cfd.dao.CertificadoDeSelloDigitalDao;
import com.luxsoft.sw3.cfd.model.CertificadoDeSelloDigital;
import com.luxsoft.sw3.cfd.model.ComprobanteFiscal;
import com.luxsoft.sw3.cfd.model.Conversiones;
import com.luxsoft.sw3.gasoc.model.Factura;
import com.luxsoft.utils.LoggerHelper;

@Service("cfdiManager")
@Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
public class CFDIManager {

	Logger logger=LoggerHelper.getLogger();
	
	private Empresa empresa;
	
	@Autowired
	private HibernateTemplate hibernateTemplate;
	
	
	public ComprobanteFiscal cargarComprobante(final Factura factura){
		String hql="from ComprobanteFiscal c where c.origen=?";
		List<ComprobanteFiscal> data=getHibernateTemplate().find(hql,factura.getId().toString());
		return data.isEmpty()?null:data.get(0);
	}
	
	
	@Transactional(propagation=Propagation.REQUIRED)
	public  ComprobanteFiscal generarCFDI(Factura factura,boolean global){
		
		final ComprobanteDocument document=ComprobanteDocument.Factory.newInstance();
		Comprobante cfd=document.addNewComprobante();
		depurar(document);
		cfd.setVersion("3.2");
		
		
		SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
		XmlDateTime xmlDateTime = XmlDateTime.Factory.newInstance();
		xmlDateTime.setStringValue(df.format(Calendar.getInstance().getTime()));
		cfd.setFecha(xmlDateTime.getCalendarValue());
		
		Venta venta=factura.getVentas().iterator().next();
		
		cfd.setTipoDeComprobante(TipoDeComprobante.INGRESO);
		cfd.setFormaDePago("PAGO EN UNA SOLA EXHIBICION");
		cfd.setMetodoDePago("NO IDENTIFICADO");
		if(StringUtils.isNotBlank(venta.getComentarioCancelacionDBF()))
			cfd.setNumCtaPago(venta.getComentarioCancelacionDBF());
		
		
		// Emisor
		final Emisor emisor=cfd.addNewEmisor();
		emisor.setNombre(getEmpresa().getNombre());
		emisor.setRfc(getEmpresa().getRfc());
		// Regimen
		String regimen=getEmpresa().getRegimen();
		String[] regs=StringUtils.split(regimen, ';');
		for(String r:regs){
			RegimenFiscal rf=emisor.addNewRegimenFiscal();
			rf.setRegimen(r);
		}
		
		// Emisor domicilioFiscal
		TUbicacionFiscal domicilioFiscal=emisor.addNewDomicilioFiscal();
		Conversiones.getTUbicacionFiscal(getEmpresa().getDireccion(),domicilioFiscal);
		// Emisor expedido en
		TUbicacion ubicacion=emisor.addNewExpedidoEn();
		Conversiones.getTUbicacion(getEmpresa().getDireccion(),ubicacion);
		cfd.setLugarExpedicion(getEmpresa().getDireccion().getPais());
		
		// Receptor
		final Cliente cliente=factura.getCliente();
		Receptor receptor=cfd.addNewReceptor();
		if(global){
			receptor.setNombre("VENTA AL PUBLICO EN GENERAL");
		}
		else{
			receptor.setNombre(cliente.getNombre());	
		}
		receptor.setRfc(CFDUtils.limpiarRfc(cliente.getRfc()));
		Direccion direccion=cliente.getDireccionFiscal();
		if(!cliente.getRfc().equals("XAXX010101000"))
			Conversiones.getTUbicacion(direccion,receptor.addNewDomicilio());
		else{
			TUbicacion ub=receptor.addNewDomicilio();
			ub.setPais("MEXICO");
		}
		
		// Conceptos
		Conceptos conceptos=cfd.addNewConceptos();
		
		if(global){
			Concepto c=conceptos.addNewConcepto();
			c.setCantidad(BigDecimal.valueOf(1));
			c.setUnidad("No Aplica");
			c.setDescripcion("Facturacion general del dia");
			c.setValorUnitario(factura.getImporte());
			CantidadMonetaria importe=CantidadMonetaria.pesos(factura.getImporte());
			c.setImporte(importe.amount());
			
			// Totales
			cfd.setSubTotal(factura.getImporte());
			cfd.setTotal(factura.getTotal());
			Impuestos impuestos=cfd.addNewImpuestos();
			impuestos.setTotalImpuestosTrasladados(factura.getImpuesto());
			Traslados traslados=impuestos.addNewTraslados();
			Traslado traslado=traslados.addNewTraslado();
			traslado.setImpuesto(Traslado.Impuesto.IVA);
			traslado.setImporte(factura.getImpuesto());
			traslado.setTasa(MonedasUtils.IVA.multiply(BigDecimal.valueOf(100)));
			cfd.setDescuento(factura.getImporteDescuento());
						
			
		}else{
			for(VentaDet det:factura.getPartidas()){
				Concepto c=conceptos.addNewConcepto();
				//c.setCantidad(new BigDecimal(det.getCantidadEnUnidad()).abs().setScale(3,RoundingMode.HALF_EVEN));
				BigDecimal cantidad=new BigDecimal(det.getCantidadEnUnidad()).setScale(2,RoundingMode.HALF_EVEN);
				c.setCantidad(cantidad.abs());
				c.setUnidad(det.getUnidad().getNombre());
				if(factura.getSerie().equals("C")){
					c.setNoIdentificacion(det.getClave());
				}else{
					if(!(det.getSocioNombre()==null))
					  c.setNoIdentificacion(det.getSocioNombre());
				}
				 
				c.setDescripcion(det.getDescripcion());
				c.setValorUnitario(det.getPrecio().setScale(2,RoundingMode.HALF_EVEN));
				CantidadMonetaria importe=CantidadMonetaria.pesos(det.getImporte());
				c.setImporte(importe.amount());
				if(cliente.getRfc().equals("XAXX010101000")){
					c.setValorUnitario(det.getPrecioConIva());
					c.setImporte(det.getImporteConIva());
				}
			}
			// Totales
			cfd.setTotal(factura.getTotal());
			cfd.setSubTotal(factura.getImporte());
			Impuestos impuestos=cfd.addNewImpuestos();
			if(cliente.getRfc().equals("XAXX010101000")){
				
				CantidadMonetaria subTotal=CantidadMonetaria.pesos(factura.getImporteBruto());
				subTotal.multiply(1d+MonedasUtils.IVA.doubleValue());
				cfd.setSubTotal(subTotal.amount());
				//cfd.setSubTotal(venta.getImporteBruto().multiply(multiplicand));			
				CantidadMonetaria  desc=CantidadMonetaria.pesos(factura.getImporteDescuento());
				//desc=desc.multiply(1d+MonedasUtils.IVA.doubleValue());
				cfd.setDescuento(desc.amount());
			}else{
				
				impuestos.setTotalImpuestosTrasladados(factura.getImpuesto());
				
				Traslados traslados=impuestos.addNewTraslados();
				Traslado traslado=traslados.addNewTraslado();
				traslado.setImpuesto(Traslado.Impuesto.IVA);
				traslado.setImporte(factura.getImpuesto());
				traslado.setTasa(MonedasUtils.IVA.multiply(BigDecimal.valueOf(100)));
				cfd.setDescuento(factura.getImporteDescuento());
			}
		}
		
		
		
		document.getComprobante().setSerie(factura.getSerie());
		document.getComprobante().setFolio(factura.getFolio());
		
		registrarCertificado(document);
		
		
		//Entidad de Comprobante Fiscal
		ComprobanteFiscal cf=new ComprobanteFiscal(document);
		cf.setTipo("FACTURA");
		cf.setFolio(factura.getFolio());
		cf.setSerie(factura.getSerie());
		cf.setOrigen(factura.getId().toString());	
		cf.setNumeroDeCertificado(document.getComprobante().getNoCertificado());
		cf.setAnoAprobacion(2012);
		cf.setNoAprobacion(1000);
		
		String fileName=MessageFormat.format("FAC_{0}_{1}",factura.getSerie(), factura.getFolio());
		cf=salvar(fileName,document,cf);
		
		return cf;
	}
	
	
	//////Generar conceptos para la nota de credito por Bonificacion
	
	private void generarConceptos(Comprobante cfd, NotaDeCredito nota){
		if(nota instanceof NotaDeCreditoBonificacion){
			NotaDeCreditoBonificacion bon=(NotaDeCreditoBonificacion)nota;
			Conceptos conceptos=cfd.addNewConceptos();
			
				
			if(bon.getConceptos().isEmpty()){
				Concepto c=conceptos.addNewConcepto();
				c.setCantidad(BigDecimal.ONE);
				c.setUnidad("No aplica");
				c.setDescripcion(bon.getComentario());
				c.setValorUnitario(bon.getImporte().setScale(2));
				c.setImporte(bon.getImporte().setScale(2));
				if(bon.getCliente().getRfc().equals("XAXX010101000")){
					c.setValorUnitario((bon.getImporte().multiply((MonedasUtils.IVA.add(new BigDecimal(1))))));
					c.setImporte(bon.getImporte().multiply((MonedasUtils.IVA.add(new BigDecimal(1)))));
				}
			}else{
				
				for(NotaDeCreditoDet det:bon.getConceptos()){
					Concepto c=conceptos.addNewConcepto();
					c.setCantidad(BigDecimal.ONE);
					c.setUnidad("No aplica");
					String pattern="Suc:{0} Docto:{1,number,#######} ({2,date,short})";						
					c.setDescripcion(MessageFormat.format(pattern
							,det.getVenta().getSucursal()
							,det.getVenta().getDocumento()
							,det.getVenta().getFecha()
							)
							);
					
					c.setValorUnitario((det.getImporte().divide((MonedasUtils.IVA.add(new BigDecimal(1))),2,RoundingMode.HALF_EVEN)).setScale(2));
					c.setImporte((det.getImporte().divide((MonedasUtils.IVA.add(new BigDecimal(1))),2,RoundingMode.HALF_EVEN)).setScale(2));
					
					if(bon.getCliente().getRfc().equals("XAXX010101000")){
						c.setValorUnitario((det.getImporte()));
						c.setImporte((det.getImporte()));
					}
				}
				
			}
		}
		
	}
	
	///////////////
	
	
	
	@Transactional(propagation=Propagation.REQUIRED)
	public  ComprobanteFiscal generarCFDINotaBonificacion(NotaDeCredito nota){
		
		final ComprobanteDocument document=ComprobanteDocument.Factory.newInstance();
		Comprobante cfd=document.addNewComprobante();
		depurar(document);
		cfd.setVersion("3.2");
		
		
		SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
		XmlDateTime xmlDateTime = XmlDateTime.Factory.newInstance();
		xmlDateTime.setStringValue(df.format(Calendar.getInstance().getTime()));
		cfd.setFecha(xmlDateTime.getCalendarValue());
		
		NotaDeCredito notaCre=nota;
		
		cfd.setTipoDeComprobante(TipoDeComprobante.EGRESO);
		cfd.setFormaDePago("PAGO EN UNA SOLA EXHIBICION");
		cfd.setMetodoDePago("NO IDENTIFICADO");
		
		// Emisor
		final Emisor emisor=cfd.addNewEmisor();
		emisor.setNombre(getEmpresa().getNombre());
		emisor.setRfc(getEmpresa().getRfc());
		// Regimen
		String regimen=getEmpresa().getRegimen();
		String[] regs=StringUtils.split(regimen, ';');
		for(String r:regs){
			RegimenFiscal rf=emisor.addNewRegimenFiscal();
			rf.setRegimen(r);
		}
		
		// Emisor domicilioFiscal
		TUbicacionFiscal domicilioFiscal=emisor.addNewDomicilioFiscal();
		Conversiones.getTUbicacionFiscal(getEmpresa().getDireccion(),domicilioFiscal);
		// Emisor expedido en
		TUbicacion ubicacion=emisor.addNewExpedidoEn();
		Conversiones.getTUbicacion(getEmpresa().getDireccion(),ubicacion);
		cfd.setLugarExpedicion(getEmpresa().getDireccion().getPais());
		
		// Receptor
		final Cliente cliente=nota.getCliente();
		Receptor receptor=cfd.addNewReceptor();
	
			receptor.setNombre(cliente.getNombre());	
	
		receptor.setRfc(CFDUtils.limpiarRfc(cliente.getRfc()));
		Direccion direccion=cliente.getDireccionFiscal();
		if(!cliente.getRfc().equals("XAXX010101000"))
			Conversiones.getTUbicacion(direccion,receptor.addNewDomicilio());
		else{
			TUbicacion ub=receptor.addNewDomicilio();
			ub.setPais("MEXICO");
		}
		
		// Conceptos
		generarConceptos(cfd, notaCre);
		
		
			// Totales
			cfd.setTotal(notaCre.getTotal());
			cfd.setSubTotal(notaCre.getImporte());
			Impuestos impuestos=cfd.addNewImpuestos();
			if(cliente.getRfc().equals("XAXX010101000")){
				
				CantidadMonetaria subTotal=notaCre.getImporteCM();
				//subTotal.multiply(MonedasUtils.IVA.doubleValue()+1d);
				//cfd.setSubTotal(subTotal.amount());
				cfd.setSubTotal(subTotal.multiply(MonedasUtils.IVA.doubleValue()+1d).amount());
				//cfd.setSubTotal(venta.getImporteBruto().multiply(multiplicand));			
				//CantidadMonetaria  desc=CantidadMonetaria.pesos(factura.getImporteDescuento());
				//desc=desc.multiply(1d+MonedasUtils.IVA.doubleValue());
			//	cfd.setDescuento(desc.amount());
			}else{
				
				impuestos.setTotalImpuestosTrasladados(notaCre.getImpuesto());
				
				Traslados traslados=impuestos.addNewTraslados();
				Traslado traslado=traslados.addNewTraslado();
				traslado.setImpuesto(Traslado.Impuesto.IVA);
				traslado.setImporte(notaCre.getImpuesto());
				traslado.setTasa(MonedasUtils.IVA.multiply(BigDecimal.valueOf(100)));
			//	cfd.setDescuento(notaCre.getImporteDescuento());
			}

		
		
		
		document.getComprobante().setSerie("BON");
		String notaFolio=notaCre.getFolio()+"";
		document.getComprobante().setFolio(notaFolio);
		
		registrarCertificado(document);
		
		
		//Entidad de Comprobante Fiscal
		ComprobanteFiscal cf=new ComprobanteFiscal(document);
		cf.setTipo("NOTA");
		cf.setFolio(notaFolio);
		cf.setSerie("BON");
		cf.setOrigen(notaCre.getId().toString());	
		cf.setNumeroDeCertificado(document.getComprobante().getNoCertificado());
		cf.setAnoAprobacion(2012);
		cf.setNoAprobacion(1000);
		
		String fileName=MessageFormat.format("NOTA_BON_{0}", notaFolio);
		cf=salvar(fileName,document,cf);
		
		return cf;
		
		
		
	}
	
	
	
	
	public void depurar(ComprobanteDocument document){
		XmlCursor cursor=document.newCursor();
		
		if(cursor.toFirstChild()){
			
			QName qname=new QName("http://www.w3.org/2001/XMLSchema-instance","schemaLocation","xsi");
			cursor.setAttributeText(qname,"http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd" );
			cursor.toNextToken();
			cursor.insertNamespace("cfdi", "http://www.sat.gob.mx/cfd/3");
		}	
	}
	
	Sellador sellador;
	CadenaOriginalBuilder cadenaBuilder;
	
	private void registrarCertificado(ComprobanteDocument document){
		if(sellador==null)
			sellador=new Sellador();
		if(cadenaBuilder==null)
			cadenaBuilder=new CadenaOriginalBuilder();
		try {
			
			byte[] encodedCert = Base64.encode(sellador.certificado.getEncoded());
			document.getComprobante().setCertificado(new String(encodedCert));
			String certificadoNum=System.getProperty("cfdi.cert.num", "00001000000201478375");
			document.getComprobante().setNoCertificado(certificadoNum);
			
			String cadenaOriginal=cadenaBuilder.obtenerCadena(document);
			String sello=sellador.getSello(cadenaOriginal);
			document.getComprobante().setSello(sello);
			
		} catch (Exception e) {
			String em=ExceptionUtils.getRootCauseMessage(e);
			logger.error(e);
			e.printStackTrace();
			throw new RuntimeException("Error procesando certificado  causa: "+em);
		}		
	}
	
	@Transactional(propagation=Propagation.MANDATORY)
	public ComprobanteFiscal salvar(String xmlName,ComprobanteDocument docto,ComprobanteFiscal cf) {
		
		try {
			cf.setTotal(docto.getComprobante().getTotal());
			cf.setImpuesto(docto.getComprobante().getImpuestos().getTotalImpuestosTrasladados());
			cf.setEstado("1");
			cf.setRfc(docto.getComprobante().getReceptor().getRfc());			
			cf.setTipoCfd(StringUtils.substring(docto.getComprobante().getTipoDeComprobante().toString(),0,1).toUpperCase());
			
			File dir=new File(getCfdDirPath());
		
			
			File destino=new File(dir+"\\in",xmlName+".xml");
			File timbrado=new File(dir,"SIGN_"+xmlName+".xml");
			
			
			cf.setXmlPath(timbrado.toURI().toURL().toString());
			registrarBitacora(cf);
			cf.getLog().setCreado(docto.getComprobante().getFecha().getTime());
			cf=(ComprobanteFiscal)hibernateTemplate.merge(cf);
			
			XmlOptions options = new XmlOptions();
			options.setCharacterEncoding("UTF-8");
			options.setSaveNamespacesFirst();
			options.setSavePrettyPrint();
			
			
			/*
			
	        options.setSaveInner();
	        options.setSaveAggressiveNamespaces();
	        options.setUseDefaultNamespace();
	        
	        */
			docto.save(destino,options);
			File respaldo=new File(getCfdDirPath()+"\\resp",xmlName+".xml");
			docto.save(respaldo,options);

			return cf;
		} catch (IOException e) {
			throw new RuntimeException(ExceptionUtils.getRootCauseMessage(e),e);
		}
	}
	
	@Transactional(propagation=Propagation.SUPPORTS,readOnly=false)
	private void registrarBitacora(ComprobanteFiscal bean){
		Date time=new Date();		
		String user=KernellSecurity.instance().getCurrentUserName();
		bean.getLog().setModificado(time);
		bean.getLog().setUpdateUser(user);
		if(bean.getId()==null){
			bean.getLog().setCreado(time);
			bean.getLog().setCreateUser(user);
		}
	}

	
	
	//private CertificadoDeSelloDigital certificadoDigital;
	/*
	public CertificadoDeSelloDigital getCertificadoDigital(){
		if(certificadoDigital==null){
			certificadoDigital=certificadoDeSelloDigitalDao.get(1L);
		}
		return certificadoDigital;
	}*/
	
	private String cfdDirPath;
	
	public String getCfdDirPath() {
		if(cfdDirPath==null){
			cfdDirPath=System.getProperty("cfd.dir.path", "C:\\gasoc\\cfdi\\xml");
		}
		return cfdDirPath;
	}
	
	
	/**
	 * Regresa la empresa registrada en el sistema
	 * 
	 */
	private Empresa getEmpresa() {
		if(empresa==null){
			return (Empresa)getHibernateTemplate().get(Empresa.class, 1L);
		}
		return empresa;
	}

	public HibernateTemplate getHibernateTemplate() {
		return hibernateTemplate;
	}

	public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
		this.hibernateTemplate = hibernateTemplate;
	}
	
	
	
	
}
